/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/error.hpp>
#include <baja/assert.hpp>

namespace baja {

#ifdef BAJA_CONFIG_TEST_BUILD

typedef std::function<void(const std::string&, void*)> testCallback;

struct itestable
{
    BAJA_INTERFACE(itestable, "2CAD138D-B6A5-406F-9B90-0099C47BC6DE")

    virtual void registerEvent(const std::string& eid, testCallback callback) = 0;
    virtual void raise(const std::string& eid, void* context) = 0;
    virtual void inspect(const std::string& iid, void** context) = 0;  
};

class testable_base
{

private:
    std::map<std::string, testCallback, std::function<bool(const std::string&, const std::string&)>> _eventMap;

public:
    testable_base()
    {
        _eventMap = std::map<std::string, testCallback, std::function<bool(const std::string&, const std::string&)>>(
            [](const std::string& left, const std::string& right) 
            { 
                return left.compare(right) < 0; 
            });
    }

    virtual ~testable_base() {}

    void registerEvent(const std::string& eid, testCallback callback)
    {
        auto iter = _eventMap.find(eid);
        if (iter != _eventMap.end())
        {
            BAJA_THROW(std::invalid_argument("id already registered"));
        }

        _eventMap[eid] = callback;
    }

    void raise(const std::string& eid, void* context)
    {
        auto iter = _eventMap.find(eid);
        if (iter == _eventMap.end())
        {
            return;
        }

        iter->second(eid, context);
    }

    template <typename T>
    T* inspect(const std::string& iid)
    {
        void* pv;
        pv = this->inspectInternal(iid);
        return static_cast<T*>(pv);
    }

protected:

    virtual void* inspectInternal(const std::string& iid)
    {
        // to be overriden by testable classes
        BAJA_THROW(std::invalid_argument("not implemented"));
    }
};
#else // if !BAJA_CONFIG_TEST_BUILD

class testable_base
{
};
#endif

#ifdef BAJA_CONFIG_TEST_BUILD
#define BAJA_TESTABLE_EVENT(eid_, ctx_) this->testable_base::raise(eid_, ctx_);
#else
#define BAJA_TESTABLE_EVENT
#endif

template <class Tmsg> 
BAJA_INLINE static void failTest(
    const char* expr,
    const Tmsg& msg
    )
{
    // using string stream so we can leverage all the << operators defined elesehwere to allow for more 
    // types to accept as parameters
    std::ostringstream sstm;

    sstm << "TEST FAILED: " << expr << ": " << msg;

    BAJA_THROW(std::logic_error(sstm.str()));
}
} // namespace baja

#define BAJA_TEST(expr, msg) (void)(!!(expr) || (::baja::failTest(#expr, msg), 0))
